package io.vividcode.happyride.tripservice.domain;

import com.google.common.collect.ImmutableList;
import io.eventuate.tram.events.aggregates.ResultWithDomainEvents;
import io.eventuate.tram.sagas.orchestration.SagaInstanceFactory;
import io.vividcode.happyride.common.PositionVO;
import io.vividcode.happyride.tripservice.api.events.CancellationParty;
import io.vividcode.happyride.tripservice.api.events.DriverAcceptTripDetails;
import io.vividcode.happyride.tripservice.api.events.DriverAcceptTripEvent;
import io.vividcode.happyride.tripservice.api.events.TripDetails;
import io.vividcode.happyride.tripservice.api.events.TripDomainEvent;
import io.vividcode.happyride.tripservice.api.web.TripVO;
import io.vividcode.happyride.tripservice.dataaccess.TripRepository;
import io.vividcode.happyride.tripservice.sagas.createtrip.CreateTripSaga;
import io.vividcode.happyride.tripservice.sagas.createtrip.CreateTripSagaState;

import java.math.BigDecimal;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;

import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
@Transactional
public class TripService {

    @Autowired
    TripRepository tripRepository;

    @Autowired
    TripFareService tripFareService;

    @Autowired
    TripDomainEventPublisher eventPublisher;

    @Autowired
    SagaInstanceFactory sagaInstanceFactory;

    @Autowired
    CreateTripSaga createTripSaga;

    public TripVO createTrip(final String passengerId, final PositionVO startPos,
                             final PositionVO endPos) {
        final ResultWithDomainEvents<Trip, TripDomainEvent> tripAndEvents = Trip
                .createTrip(passengerId, startPos, endPos);
        final BigDecimal fare = this.tripFareService.calculate(startPos, endPos);
        final Trip trip = tripAndEvents.result;
        trip.setFare(fare);
        this.tripRepository.save(trip);
        this.eventPublisher.publish(trip, tripAndEvents.events);

        final TripDetails tripDetails = new TripDetails(passengerId, startPos,
                endPos);
        final CreateTripSagaState data = new CreateTripSagaState(trip.getId(),
                tripDetails, fare);
        this.sagaInstanceFactory.create(this.createTripSaga, data);
        return trip.toTripVO();
    }

    public Optional<TripVO> getTrip(final String id) {
        return this.tripRepository.findById(id).map(Trip::toTripVO);
    }

    public void markTripAsDispatched(final String tripId) {
        this.updateTrip(tripId, Trip::markAsDispatched);
    }

    public void driverAcceptTrip(final String tripId, final String driverId,
                                 final BigDecimal posLng,
                                 final BigDecimal posLat) {
        this.withTrip(tripId, trip -> this.eventPublisher.publish(trip,
                ImmutableList
                        .of(new DriverAcceptTripEvent(
                                new DriverAcceptTripDetails(driverId, posLng, posLat)))));
    }

    public void markTripAsAccepted(final String tripId, final String driverId) {
        this.updateTrip(tripId, trip -> trip.acceptByDriver(driverId));
    }

    public void markTripAsStarted(final String tripId) {
        this.updateTrip(tripId, Trip::startTrip);
    }

    public void markTripAsFinished(final String tripId) {
        this.updateTrip(tripId, Trip::finishTrip);
    }

    public void markTripAsFailed(final String tripId) {
        this.updateTrip(tripId, Trip::markAsFailed);
    }

    public void rejectTrip(final String tripId) {
        this.updateTrip(tripId, Trip::rejectTrip);
    }

    public void confirmTrip(final String tripId) {
        this.updateTrip(tripId, Trip::confirmTrip);
    }

    public void shouldCancel(final String tripId,
                             final CancellationParty initiator) {
        this.updateTrip(tripId, trip -> trip.shouldCancel(initiator));
    }

    public void shouldNotCancel(final String tripId,
                                final CancellationParty initiator) {
        this.updateTrip(tripId, trip -> trip.shouldNotCancel(initiator));
    }

    private void updateTrip(final String tripId,
                            final Function<Trip, ResultWithDomainEvents<Trip, TripDomainEvent>> updater) {
        this.withTrip(tripId,
                trip -> this.saveAndPublishEvents(updater.apply(trip)));
    }

    private void withTrip(final String tripId, final Consumer<Trip> consumer) {
        this.tripRepository.findById(tripId).ifPresent(consumer);
    }

    private void saveAndPublishEvents(
            final ResultWithDomainEvents<Trip, TripDomainEvent> result) {
        final Trip trip = result.result;
        this.tripRepository.save(trip);
        this.eventPublisher.publish(trip, result.events);
    }
}
